package cn.ad.server.web.spring.service.biz;

import cn.ad.server.common.utils.SpringContextUtils;
import cn.ad.server.common.utils.monitor.MonitorHandler;
import cn.ad.server.persistence.ConfigRepository;
import cn.ad.server.web.filter.bid.*;
import cn.ad.server.web.spring.model.AdImpression;
import cn.ad.server.web.spring.model.AdNative;
import cn.ad.server.web.spring.model.Exchange;
import cn.ad.server.web.spring.model.adview.AdViewActionType;
import cn.ad.server.web.spring.model.adview.BidFlag;
import cn.ad.server.web.spring.model.adview.RequestAsset;
import cn.ad.server.web.spring.service.actions.context.BidContext;
import cn.ad.server.web.spring.service.actions.targeturl.ConvertTargetUrlHandler;
import cn.ad.server.biz.internal.AdService;
import cn.ad.server.domain.ad.*;
import cn.ad.server.domain.enumration.ActionType;
import cn.ad.server.domain.enumration.AdFormat;
import cn.ad.server.domain.enumration.FeeType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * @author dongbin.yu
 * @from 2016-07-28
 * @since V2.0
 */
public abstract class ImpressionMatchService<T> {

	private List<BidMatcherChain> matcherChain;

	@Autowired
	protected AdService adService;

	@Autowired
	private ConfigRepository configRepository;

	@SuppressWarnings("unchecked")
	protected T strategyFilter(Set<Strategy> strategyList, BidContext bidContext, AdTemplate adTemplate,
                               AdNative adNative, int width, int height, boolean serverFilter, int platformId, AdSpace adSpace) {

		ConcurrentMap<Integer, HashSet<Entry>> campaignCache = new ConcurrentHashMap<>();
		HashSet<Entry> entryList = new HashSet<>(10);
		if (platformId == 0)
			platformId = Integer.parseInt(getExchange().getValue());

		HashSet<Strategy> strategySet = new HashSet<>(10);

		filter: for (Strategy strategy : strategyList) {

//			//TODO TEST
//			if(strategy.getId()==259){
//				continue;
//			}
			
			if (strategy.getCampaign() == null) {
				MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
						configRepository.fetchDebugMode(), Strategy.class.getName(), String.valueOf(strategy.getId()),
						"campaign is null");
				continue;
			}
			int campaignId = strategy.getCampaign().getId();

			HashSet<Entry> strategyEntryList = new HashSet<>(10);

			for (BidMatcherChain chain : matcherChain) {
				if (serverFilter && chain instanceof StrategyFormatMatcher) {
					continue;
				}
				// 当不是sts且chain是CreativeInteractionMatcher时，跳过这个过滤。
				if (!serverFilter && chain instanceof CreativeInteractionMatcher) {
					continue;
				}

				if (!chain.match(strategy, bidContext)) {
					// if (!CollectionUtils.isEmpty(bidContext.getUserDmpId()))
					// {
					// System.out.println(strategy.getId() + ":" +
					// chain.getClass().getSimpleName());
					// }

					MonitorHandler.monitorStrategy(configRepository.fetchMonitoredStrategy(),
							configRepository.fetchDebugMode(), Strategy.class.getName(),
							String.valueOf(strategy.getId()), chain.getClass().getSimpleName());
					continue filter;
				}
			}

			Campaign campaign = adService.fetchCampaignById(strategy.getCampaign().getId());
			if (campaign == null)
				continue;

			Set<Creative> creativeList = this.getStrategyCreatives(strategy, campaign.getCreatives());
			if (!serverFilter) {
				if (campaignCache.containsKey(campaignId) && CollectionUtils.isEmpty(creativeList)) {
					HashSet<Entry> entryCampaignList = campaignCache.get(campaignId);

					for (Entry entry : entryCampaignList) {

						// if (!this.matchCreative(strategy, entry)) {
						// continue;
						// }

						Entry cloneEntry = (Entry) entry.clone();
						cloneEntry.strategyId = strategy.getId();
						cloneEntry.priority = strategy.getPriority();
						cloneEntry.price = strategy.getUnitPrice();
						cloneEntry.feeType = strategy.getFeeType() == FeeType.CPC.getValue() ? "CPC" : "CPM";
						entryList.add(cloneEntry);

						strategyEntryList.add(entry);
						strategySet.add(strategy);
					}

					continue;
				}
				 else{
					 //TODO 暂时发现GDT/BES/ADVIEW需要加creativeList = campaign.getCreatives();其他需要再确认。
					 if(platformId== Integer.valueOf(Exchange.GDT.getValue())||platformId== Integer.valueOf(Exchange.BES.getValue())||platformId== Integer.valueOf(Exchange.ADVIEW.getValue())){
						 if(CollectionUtils.isEmpty(creativeList)){
							 creativeList = campaign.getCreatives();
						 }
					 }
				 }
			} else {
				creativeList = new HashSet<>(strategy.getMatchCreatives());
			}

			if (creativeList == null)
				continue;

			if (creativeList != null) {
				for (Creative creative : creativeList) {

					SingleCreativeInteractionMatcher singleCreativeInteractionMatcher = new SingleCreativeInteractionMatcher();
					if (!singleCreativeInteractionMatcher.match(creative, bidContext)) {
						continue;
					}

					AdFormat adFormat = creative.getAdFormat();
					
//					//TODO TEST
//					if(strategy.getCampaign().getId()==999){
//						bidContext.setAdFormat(adFormat.getValue());
//					}
					
					if (adFormat.getValue() == bidContext.getAdFormat()) {

						switch (adFormat) {
						case BANNER:
						case TABLE_SCREEN:
						case OPEN_SCREEN:

							Set<AdMaterial> adMaterials = creative.getAdMaterialSet();
							if (adMaterials != null) {
								for (AdMaterial adMaterial : adMaterials) {

									List<AuditPassedMarker> auditPassedMarkers = adMaterial.getAuditPassedMarkers();
									if (platformCheck(auditPassedMarkers, platformId)) {
										AdSize adSize = adMaterial.getAdSize();

										if (adSize.getWidth() == width && adSize.getHeight() == height) {

											// if (!this.matchCreative(strategy,
											// creative.getId())) {
											// continue;
											// }

											Entry entry = new Entry();
											entry.priority = strategy.getPriority();
											entry.strategyId = strategy.getId();
											entry.campaignId = campaignId;
											entry.platformId = platformId;
											entry.adSpaceId = adSpace != null ? adSpace.getId() : 0;
											entry.creativeId = creative.getId();
											if (!CollectionUtils.isEmpty(auditPassedMarkers)) {
												entry.auditId = auditPassedMarkers.get(0).getAuditId();
											}
											fetchCreativeTargetUrl(creative, entry);
											entry.adFormat = adFormat.getValue();
											entry.price = strategy.getUnitPrice();
											entry.width = width;
											entry.height = height;
											entry.feeType = strategy.getFeeType() == FeeType.CPC.getValue() ? "CPC"
													: "CPM";
											entry.materialId = adMaterial.getId();
											entry.materialUrl = adMaterial.getMaterialUrl();
											entry.impressionTracking = creative.getImpressionTracking();
											entry.clicksTracking = creative.getClicksTracking();

											entry.setCreatives(creativeList);

											entryList.add(entry);
											strategyEntryList.add(entry);
											strategySet.add(strategy);
										}
									}
								}
							}
							break;
						case VIDEO:
							break;
						case AD_NATIVE:

							List<AuditPassedMarker> auditPassedMarkers = creative.getAuditPassedMarkers();
							if (platformCheck(auditPassedMarkers, platformId)) {

								int templateId = 0;
								for (AdMaterialGroup adMaterialGroup : (serverFilter ? creative.getAdMaterialGroups()
										: creative.getAdMaterialGroupSet())) {
									templateId = adMaterialGroup.getAdTemplate().getId();
									break;
								}
								// FIXME 传入的参数要做判断
								// adTemplate也会出现nullpointException
								if (templateId != adTemplate.getId()) {
									continue;
									// } else if (!this.matchCreative(strategy,
									// creative.getId())) {
									// continue;
								} else {
									Entry entry = new Entry();
									entry.priority = strategy.getPriority();
									entry.strategyId = strategy.getId();
									entry.campaignId = campaignId;
									entry.platformId = platformId;
									entry.adSpaceId = adSpace != null ? adSpace.getId() : 0;
									entry.creativeId = creative.getId();
									if (!CollectionUtils.isEmpty(auditPassedMarkers)) {
										entry.auditId = auditPassedMarkers.get(0).getAuditId();
									}
									fetchCreativeTargetUrl(creative, entry);
									entry.adFormat = adFormat.getValue();
									entry.price = strategy.getUnitPrice();
									entry.feeType = strategy.getFeeType() == FeeType.CPC.getValue() ? "CPC" : "CPM";
									entry.templateId = adTemplate.getId();
									entry.adMaterialGroups = serverFilter
											? new HashSet<>(creative.getAdMaterialGroups())
											: creative.getAdMaterialGroupSet();
									entry.impressionTracking = creative.getImpressionTracking();
									entry.clicksTracking = creative.getClicksTracking();

									entry.setCreatives(creativeList);

									storeTemplateSpec(entry, adNative);

									entryList.add(entry);
									strategyEntryList.add(entry);
									strategySet.add(strategy);
								}
							}

							break;
						default:
							break;

						}
					}

				}

				// Iterator<Entry> it = entryList.iterator();
				// while (it.hasNext()) {
				// Entry entry = it.next();
				//
				// if (!this.matchCreative(strategy, entry, creativeList)) {
				// it.remove();
				// strategySet.remove(strategy);
				// //continue;
				// }
				//
				//// SingleCreativeInteractionMatcher
				// singleCreativeInteractionMatcher = new
				// SingleCreativeInteractionMatcher();
				//// Creative c = selectCreative(creativeList, entry);
				//// if (c != null) {
				//// if (!singleCreativeInteractionMatcher.match(c, bidContext))
				// {
				//// it.remove();
				//// strategySet.remove(strategy);
				//// }
				//// }
				// }

			}

			campaignCache.putIfAbsent(campaignId, strategyEntryList);

		}

		if (entryList.size() == 0) {
			return null;
		}

		Set<Entry> priorityEntryList = priorityEntry(entryList);

		int r = new Random().nextInt(priorityEntryList.size());
		Entry entry = new ArrayList<>(priorityEntryList).get(r);

		bidRecordLog(bidContext, entry);

		storeGuid(bidContext, entry, adSpace, toMatchStrategy(strategySet, entry));

		return compositeBid(bidContext, entry);

	}

	Set<Creative> getStrategyCreatives(Strategy s, Set<Creative> campaignCreatives) {
		Set<Creative> cs = new HashSet<Creative>();
		if(CollectionUtils.isEmpty(campaignCreatives))
			return cs;
		//TODO 需要小晨确认
		if(s.getSpecifyCreativeIds()==null||s.getSpecifyCreativeIds().size()==0){
			return cs;
		}
		for (Creative c : campaignCreatives) {
			if (s.getSpecifyCreativeIds().contains(c.getId())) {
				cs.add(c);
			}
		}
		return cs;
	}

	Creative selectCreative(Set<Creative> creatives, Entry entry) {
		for (Creative c : creatives) {
			if (c.getId() == entry.getCreativeId())
				return c;
		}
		return null;
	}

	// Entry aa(Strategy s, Set<Entry> entryList) {
	// for(Entry e : entryList) {
	// for(Creative c : e.getCreatives()) {
	// if(matchCreative(s, c.getId()))
	// return e;
	// }
	// }
	// return null;
	// }

	boolean matchCreative(Strategy s, Entry entry, Set<Creative> creatives) {
		if (s == null)
			return true;
		if (s.getId() != entry.getStrategyId())
			return true;
		if (CollectionUtils.isEmpty(s.getSpecifyCreativeIds()))
			return true;
		if (entry.getCreativeId() == 0)
			return false;
		return s.getSpecifyCreativeIds().contains(entry.getCreativeId());
	}

	Strategy toMatchStrategy(Set<Strategy> strategySet, Entry e) {
		for (Strategy s : strategySet) {
			if (s.getId() == e.getStrategyId())
				return s;
		}
		return null;
	}

	private boolean platformCheck(List<AuditPassedMarker> auditPassedMarkers, int platformId) {

		//TODO TEST
//		if(platformId==6){
//			return true;
//		}
		
		if (auditPassedMarkers == null) {
			return false;
		}
		for (AuditPassedMarker auditPassedMarker : auditPassedMarkers) {
			if (auditPassedMarker.getPlatformId() == platformId) {
				return true;
			}
		}
		return false;
	}

	public abstract Exchange getExchange();

	public abstract T handler(AdImpression impression, BidContext bidContext);

	public abstract T compositeBid(BidContext bidContext, Entry entry);

	public abstract void storeTemplateSpec(Entry entry, AdNative adNative);

	protected class Entry implements Cloneable {

		private int platformId;

		private long adSpaceId;

		private long strategyId;

		private int priority;

		private long creativeId;

		private int campaignId;

		private int adFormat;

		private BigDecimal price;

		/**
		 * CPC | CPM
		 */
		private String feeType;

		/**
		 * 0:null,1:direct,2:download
		 */
		private int interactionType;

		private long materialId;

		private int screenPosition;

		private int[] excludeCreativeType;

		private int[] includeMimes;

		private String auditId;

		/**
		 * 素材地址
		 */
		private String materialUrl;

		private String targetUrl;

		private int width;

		private int height;

		private int templateId;

		private Set<AdMaterialGroup> adMaterialGroups;

		private RequestAsset[] assets;

		private String impressionTracking;

		private String clicksTracking;

		private Set<Creative> creatives;

		public Set<Creative> getCreatives() {
			return creatives;
		}

		public void setCreatives(Set<Creative> creatives) {
			this.creatives = creatives;
		}

		public long getAdSpaceId() {
			return adSpaceId;
		}

		public void setAdSpaceId(long adSpaceId) {
			this.adSpaceId = adSpaceId;
		}

		public int getPlatformId() {
			return platformId;
		}

		public void setPlatformId(int platformId) {
			this.platformId = platformId;
		}

		public String getAuditId() {
			return auditId;
		}

		public void setAuditId(String auditId) {
			this.auditId = auditId;
		}

		public int[] getExcludeCreativeType() {
			return excludeCreativeType;
		}

		public void setExcludeCreativeType(int[] excludeCreativeType) {
			this.excludeCreativeType = excludeCreativeType;
		}

		public int[] getIncludeMimes() {
			return includeMimes;
		}

		public void setIncludeMimes(int[] includeMimes) {
			this.includeMimes = includeMimes;
		}

		public int getScreenPosition() {
			return screenPosition;
		}

		public void setScreenPosition(int screenPosition) {
			this.screenPosition = screenPosition;
		}

		public String getTargetUrl() {
			return targetUrl;
		}

		public void setTargetUrl(String targetUrl) {
			this.targetUrl = targetUrl;
		}

		public int getPriority() {
			return priority;
		}

		public void setPriority(int priority) {
			this.priority = priority;
		}

		public long getStrategyId() {
			return strategyId;
		}

		public void setStrategyId(long strategyId) {
			this.strategyId = strategyId;
		}

		public long getCreativeId() {
			return creativeId;
		}

		public void setCreativeId(long creativeId) {
			this.creativeId = creativeId;
		}

		public int getCampaignId() {
			return campaignId;
		}

		public void setCampaignId(int campaignId) {
			this.campaignId = campaignId;
		}

		public int getAdFormat() {
			return adFormat;
		}

		public void setAdFormat(int adFormat) {
			this.adFormat = adFormat;
		}

		public BigDecimal getPrice() {
			return price;
		}

		public void setPrice(BigDecimal price) {
			this.price = price;
		}

		public String getFeeType() {
			return feeType;
		}

		public void setFeeType(String feeType) {
			this.feeType = feeType;
		}

		public int getInteractionType() {
			return interactionType;
		}

		public void setInteractionType(int interactionType) {
			this.interactionType = interactionType;
		}

		public long getMaterialId() {
			return materialId;
		}

		public void setMaterialId(long materialId) {
			this.materialId = materialId;
		}

		public String getMaterialUrl() {
			return materialUrl;
		}

		public void setMaterialUrl(String materialUrl) {
			this.materialUrl = materialUrl;
		}

		public int getWidth() {
			return width;
		}

		public void setWidth(int width) {
			this.width = width;
		}

		public int getHeight() {
			return height;
		}

		public void setHeight(int height) {
			this.height = height;
		}

		public int getTemplateId() {
			return templateId;
		}

		public void setTemplateId(int templateId) {
			this.templateId = templateId;
		}

		public Set<AdMaterialGroup> getAdMaterialGroups() {
			return adMaterialGroups;
		}

		public void setAdMaterialGroups(Set<AdMaterialGroup> adMaterialGroups) {
			this.adMaterialGroups = adMaterialGroups;
		}

		public RequestAsset[] getAssets() {
			return assets;
		}

		public void setAssets(RequestAsset[] assets) {
			this.assets = assets;
		}

		public String getImpressionTracking() {
			return impressionTracking;
		}

		public void setImpressionTracking(String impressionTracking) {
			this.impressionTracking = impressionTracking;
		}

		public String getClicksTracking() {
			return clicksTracking;
		}

		public void setClicksTracking(String clicksTracking) {
			this.clicksTracking = clicksTracking;
		}

		@SuppressWarnings("unchecked")
		public Object clone() {
			Entry entry = null;

			try {
				entry = (Entry) super.clone();
			} catch (CloneNotSupportedException e) {
				;
			}

			return entry;
		}

	}

	@PostConstruct
	public void adViewInit() {
		matcherChain = new ArrayList<>();
		matcherChain.add(new StrategyFormatMatcher());
		matcherChain.add(new TimeRangeMatcher());
		matcherChain.add(SpringContextUtils.getBean(BidFloorMatcher.class));
		matcherChain.add(new GeoTargetMatcher());
		matcherChain.add(new DeviceMatcher());
		matcherChain.add(new DayTargetMatcher());
		matcherChain.add(new CreativeInteractionMatcher());// 添加创意交互过滤。
		matcherChain.add(new BalanceMatcher());
	}

	private void fetchCreativeTargetUrl(Creative creative, Entry entry) {

		// 获取对应的动作类型
		ActionType actionType = ActionType.actionTypeMap.get(creative.getInteractionType());

		if (actionType == ActionType.LINK_JUMP) {
			InteractionRedirects interactionRedirects = creative.getInteractionRedirects();
			if (interactionRedirects != null) {
				entry.targetUrl = interactionRedirects.getTargetUrl();
			}
		} else if (actionType == ActionType.APP_DOWNLOAD) {
			InteractionApp interactionApp = creative.getInteractionApp();
			if (interactionApp != null) {
				entry.targetUrl = interactionApp.getDownloadUrl();
			}
		}

		entry.interactionType = AdViewActionType.FORMAT_MAP.get(actionType);
	}

	private Set<Entry> priorityEntry(Set<Entry> entryList) {

		if (entryList.size() == 1) {
			return entryList;
		}

		int minPriority = 0;
		Set<Entry> filterEntry = new HashSet<>(entryList.size());
		for (Entry entry : entryList) {
			int priority = entry.priority;
			if (minPriority == 0) {
				minPriority = priority;
				filterEntry.add(entry);
			} else {
				if (priority < minPriority) {
					filterEntry.clear();
					filterEntry.add(entry);
					minPriority = priority;
				} else if (priority == minPriority) {
					filterEntry.add(entry);
				}
			}
		}

		return filterEntry;

	}

	/**
	 * 记录竞价信息
	 *
	 * @param bidContext
	 * @param entry
	 */
	private void bidRecordLog(BidContext bidContext, Entry entry) {

		bidContext.setCampaignId(entry.campaignId);
		bidContext.setStrategyId(entry.strategyId);
		bidContext.setCreativeId(entry.creativeId);
		bidContext.setTemplateId(entry.templateId);
		bidContext.setMaterialId(entry.getMaterialId());
		bidContext.setAdFormat(entry.getAdFormat());
		// 实际出价
		bidContext.setBidPrice(entry.getPrice() != null ? entry.getPrice().doubleValue() : 0.0);

		bidContext.setBidPriceType(entry.feeType);
		bidContext.setFlag(BidFlag.SUCCESS.getValue());

	}

	// TODO 将系统中所有生成 GUID的方法整合到一个方法中
	private void storeGuid(BidContext bidContext, Entry entry, AdSpace adSpace, Strategy s) {

		// 生成guid
		GuidValueObject guidVo = new GuidValueObject();

		guidVo.setCid(entry.creativeId);
		guidVo.setMid(entry.materialId);
		guidVo.setMuniqueId(bidContext.getMuniqueId());
		guidVo.setMuniqueType(bidContext.getMuniqueType());
		guidVo.setBrand(bidContext.getBrand());
		guidVo.setOs(bidContext.getOs());
		guidVo.setPlatform(bidContext.getModal());
		guidVo.setDeviceSize(bidContext.getScreenWidth() + "*" + bidContext.getScreenHeight());
		guidVo.setNetwork(bidContext.getConnType());
		guidVo.setOperator(bidContext.getMobOps());
		guidVo.setIp(bidContext.getIp());
		guidVo.setLongitude(bidContext.getLongitude());
		guidVo.setLatitude(bidContext.getLatitude());
		guidVo.setDeviceType(bidContext.getDeviceType());
		guidVo.setMac(bidContext.getMac());
		guidVo.setMac1(bidContext.getMac1());
		guidVo.setImei(bidContext.getImei());
		guidVo.setAnid(bidContext.getAnid());
		guidVo.setAaid(bidContext.getAaid());
		guidVo.setIdfa(bidContext.getIdfa());
		guidVo.setUdid(bidContext.getUdid());
		guidVo.setDuid(bidContext.getDuid());
		guidVo.setImeisha1(bidContext.getImeisha1());
		guidVo.setDpidsha1(bidContext.getDpidsha1());
		guidVo.setMacsha1(bidContext.getMacsha1());
		guidVo.setImeimd5(bidContext.getImeimd5());
		guidVo.setDpidmd5(bidContext.getDpidmd5());
		guidVo.setMacmd5(bidContext.getMacmd5());
		guidVo.setCustomId(bidContext.getCustomId());
		guidVo.setOpenUdid(bidContext.getOpenUdid());

		guidVo.setFid(bidContext.getAdFormat());
		guidVo.setTemplateId(bidContext.getTemplateId());

		bidContext.setGuid(adService.storeGuid(guidVo, bidContext.getAdxType(), adSpace, s));
	}

	// /**
	// * 填充TrackCommand
	// *
	// *
	// 注：STSAdRequestAction，STSAdProcessor，ImpressionHandler，ImpressionMatchService
	// 中为同一份,有点不同。代码冗余。
	// * @param bidContext
	// * @param command
	// * @return
	// */
	// protected TrackCommand populateCommand(BidContext bidContext,TrackCommand
	// command){
	// if(command==null){
	// command = new TrackCommand();
	// }
	//// os={os}&mac={mac}&mac_md5={mac_md5}&idfa={idfa}
	//// &idfa_md5={idfa_md5}&imei={imei}&imei_md5={imei_md5}&
	//// androidid={androidid}&androidid_md5={androidid_md5}
	//// &aid={aid}&sid={sid}&cid={cid}&mid={mid}&guid={guid}
	// command.setOs(bidContext.getOs());
	// command.setMac(bidContext.getMac());
	// command.setMac_md5(bidContext.getMacmd5());
	// command.setIdfa(bidContext.getIdfa());
	// command.setIdfa_md5(bidContext.getDpidmd5());
	// command.setImei(bidContext.getImei());
	// command.setImei_md5(bidContext.getImeimd5());
	// command.setAndroidid(bidContext.getAnid());
	// command.setAndroidid_md5(bidContext.getDpidmd5());
	// command.setAid(0l);
	// command.setSid(bidContext.getStrategyId());
	// command.setCid(bidContext.getCreativeId());
	// command.setMid(bidContext.getMaterialId());
	// command.setGuid(bidContext.getGuid());
	// return command;
	// }

	@Resource(name = "targetUrlConvert")
	public Map<String, cn.ad.server.web.spring.service.actions.targeturl.IConvertTargetUrl> targetUrlConvert;

	@Autowired
	public ConvertTargetUrlHandler convertTargetUrlHandler;

}
